﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace Networking
{
    public class ServerManager
    {
        public int Port { get; set; }
        private UdpClient Server;
        private Dictionary<int, string> Clients;
        public delegate void ServerInitEventHandler(int port);
        public delegate void ClientConnectEventHandler(int clientid, string ip);
        public delegate void ClientDisconnectEventHandler(int clientid);
        public delegate void ReceiveClientDataEventHandler(int clientid, Headers header, SubHeaders subheader, string data);
        public event ClientConnectEventHandler OnClientConnect;
        public event ClientDisconnectEventHandler OnClientDisconnect;
        public event ReceiveClientDataEventHandler OnReceiveClientData;
        public event ServerInitEventHandler OnServerInit;

        public ServerManager(int port)
        {
            Port = port;
            Server = new UdpClient(7777);
            Clients = new Dictionary<int, string>();
        }

        public void Start()
        {
            try
            {               
                if (OnServerInit != null) OnServerInit(Port);
                Thread thrdAcceptClient = new Thread(new ThreadStart(AcceptClientData));
                thrdAcceptClient.Start();
            }
            catch (Exception ex)
            {
                Console.WriteLine("ERROR: " + ex.ToString()); //Debug Error
            }
        }

        public int GetClientID(string addr)
        {
            foreach (KeyValuePair<int, string> pair in Clients)
            {
                if (pair.Value == addr) return pair.Key;
            }

            return 0;
        }

        public string GetClientAddress(int clientid)
        {
            foreach (KeyValuePair<int, string> pair in Clients)
            {
                if (pair.Key == clientid) return pair.Value;
            }

            return "Invalid";
        }

        public void SendData(int clientid, SubHeaders subheader, string data)
        {
            try
            {
                string addr = GetClientAddress(clientid);
                PacketWriter packetwriter = new PacketWriter();
                packetwriter.Write((ushort)Headers.ServerToClient);
                packetwriter.Write((ushort)subheader);
                packetwriter.Write(data);
                byte[] databuffer = packetwriter.GetBytes();
                Server.Send(databuffer, databuffer.Length, new IPEndPoint(IPAddress.Parse(addr), Port));
            }
            catch (Exception ex)
            {
                Console.WriteLine("ERROR: " + ex.ToString()); //Debug Error
            }
        }

        public void SendObject(int clientid, SubHeaders subheader, object obj)
        {
            try
            {
                string addr = GetClientAddress(clientid);
                PacketWriter packetwriter = new PacketWriter();
                packetwriter.Write((ushort)Headers.ServerToClient);
                packetwriter.Write((ushort)subheader);
                packetwriter.WriteObject(obj);
                byte[] databuffer = packetwriter.GetBytes();
                Server.Send(databuffer, databuffer.Length, new IPEndPoint(IPAddress.Parse(addr), Port));
            }
            catch (Exception ex)
            {
                Console.WriteLine("ERROR: " + ex.ToString()); //Debug Error
            }
        }

        public void SendDataToAll(SubHeaders subheader, string data, int clientid = -1)
        {
            if (clientid == -1)
            {
                for (int i = 1; i <= Clients.Count; i++)
                {
                    SendData(i, subheader, data);
                }
            }
            else
            {
                for (int i = 1; i <= Clients.Count; i++)
                {
                    if (clientid != i) SendData(i, subheader, data);
                }
            }
        }

        public void SendObjectToAll(SubHeaders subheader, object obj, int clientid = -1)
        {
            if (clientid == -1)
            {
                for (int i = 1; i <= Clients.Count; i++)
                {
                    SendObject(i, subheader, obj);
                }
            }
            else
            {
                for (int i = 1; i <= Clients.Count; i++)
                {
                    if (clientid != i) SendObject(i, subheader, obj);
                }
            }
        }

        private void AcceptClientData()
        {
            IPEndPoint endpoint;
            byte[] data;
            PacketReader packetreader;
            Headers header;
            SubHeaders subheader;
            string info;
            string addr;
            int clientid;

            while (true)
            {
                endpoint = new IPEndPoint(IPAddress.Any, 7777);
                addr = endpoint.Address.ToString();
                data = Server.Receive(ref endpoint);
                packetreader = new PacketReader(data);
                header = (Headers)packetreader.ReadInt16();
                subheader = (SubHeaders)packetreader.ReadInt16();
                info = packetreader.ReadString();

                if (header == Headers.ClientConnect)
                {
                    HandleClientConnection(endpoint);
                }
                else
                {
                    clientid = GetClientID(addr);
                    if (OnReceiveClientData != null) OnReceiveClientData(clientid, header, subheader, info);
                }
            }
        }

        private void HandleClientConnection(IPEndPoint endpoint)
        {
            string addr = endpoint.Address.ToString();

            lock (Clients)
            {
                if (!Clients.ContainsValue(addr))
                {
                    Clients.Add(Clients.Count+1, addr);
                    if (OnClientConnect != null) OnClientConnect(Clients.Count, addr); 
                }
            }
        }
    }
}
